#include "log.hpp"

Log::log_level cur_log_level=Log::INFO_LOG_LEVEL;

// 初始化日志类
bool Log::init(const char *file_name, int buffer_queue_size, Log::log_level level,
          int buffer_size, int split_lines)
{
    // 重复初始化，则返回失败
    if (_log_inited)
    {
        WAR_LOG("logger has been initialized,don't try again!\n");
        return false;
    }

    // 两种输出模式：输出文件，输出屏幕
    // 传入文件名则输出文件，文件名为空则输出屏幕
    if (!file_name)
    {
        _log_stdout = true;
    }

    // 规定文件名小于128字节
    if (!_log_stdout && strlen(file_name) >= 128)
    {
        ERR_LOG("file name must be less then 128 bytes!\n");
        exit(-1);
        // throw
    }

    // 设置日志等级
    set_log_level(level);

    // 设置异步输入输出线程和阻塞队列
    if (buffer_queue_size >= 1)
    {
        _log_is_async = true;
        _log_queue = new Log_buffer_queue<std::string>(buffer_queue_size);
        _log_async_w_thread = new std::thread(&Log::async_flush);
    }

    // 初始化缓冲区
    _log_buf_size = buffer_size;
    _log_buf = new char[_log_buf_size];
    // 格式化缓冲区
    memset(_log_buf, '\0', _log_buf_size);
    _log_split_lines = split_lines;

    // 获取当前日期
    my_time tm = get_current_time();
    _log_today = tm.day;

    // 屏幕输出时
    if (_log_stdout)
    {
        _log_inited = true;
        _log_fp = stdout;
        DBG_LOG("succeed in using stdout as log output!\n");
        DBG_LOG("log init finished!\n");
        return true;
    }

    // 文件输出时
    char log_file_fullname[268] = {0};
    // strrchr:查找字符串中最后出现的位置
    const char *p = strrchr(file_name, '/');
    if (p == nullptr)
    {
        ERR_LOG("log filename shoule behind '/'\n");
        return false;
    }
    else
    {
        // 最终在_log_dir_name的目录下，生成“年_月_日名字”的文件
        strcpy(_log_file_name, p + 1);
        strncpy(_log_dir_name, file_name, p - file_name + 1);
        snprintf(log_file_fullname, 267, "%s%04d_%02d_%02d_%s", _log_dir_name,
                 tm.year, tm.month, tm.day, _log_file_name);
        // 追加到文件，若不存在，则创建
        _log_fp = fopen(log_file_fullname, "a");
    }

    if (_log_fp == nullptr)
    {
        ERR_LOG("open %s failed!\n", log_file_fullname);
        return false;
    }

    _log_inited = true;
    DBG_LOG("succeed in using file %s as log output!\n", log_file_fullname);
    DBG_LOG("log init finished!\n");

    return true;
}

bool Log::is_init()
{
    return _log_inited;
}

// 写入日志
void Log::write_log(const char *file_name, const char *tn_callbackname, int line_no,
               Log::log_level level, const char *message, ...)
{
    my_time tm = get_current_time();

    // 日志数量加1，当新的一天或日志超出限制，改变新的文件路径
    {
        std::lock_guard<std::mutex> mtx(_log_mutex);
        _log_count++;

        if (_log_today != tm.day || _log_count % _log_split_lines == 0)
        {
            DBG_LOG("create new log file\n");
            char new_file_name[301] = {0};
            fflush(_log_fp);
            fclose(_log_fp);
            char prefix[24] = {0};
            snprintf(prefix, 23, "%04d_%02d_%02d_", tm.year, tm.month, tm.day);

            // 两种情况：新的一天，日志超出限制
            if (_log_today != tm.day)
            {
                snprintf(new_file_name, 300, "%s%s%s", _log_dir_name, prefix, _log_file_name);
                _log_today = tm.day;
                _log_count = 0;
            }
            else
            {
                snprintf(new_file_name, 300, "%s%s%s.%lld", _log_dir_name, prefix, _log_file_name, _log_count / _log_split_lines);
            }
            _log_fp = fopen(new_file_name, "a");
        }
    }

    // 格式化日志内容
    // va_list是一个获取可变参数列表，配合vsnprintf使用，需要va_start初始化，va_end释放
    va_list valist;
    va_start(valist, message);
    std::string log_str;
    {
        std::lock_guard<std::mutex> tex(_log_mutex);
        int n = snprintf(_log_buf, 300, "%04d-%02d-%02d %02d:%02d:%02d %s [%s:%s:%d] ",
                         tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second,
                         log_level_name[level], file_name, tn_callbackname, line_no);
        int m = vsnprintf(_log_buf + n, _log_buf_size - 1, message, valist);
        _log_buf[n + m] = '\n';
        _log_buf[n + m + 1] = '\0';
        log_str = _log_buf;
    }
    va_end(valist);

    if (_log_is_async)
    {
        // 使用日志线程向队列写入内容
        // 将日志插入队列，若插入失败，则循环插入，等待队列有位置存在
        while (!_log_queue->push(log_str) && !_log_thread_stop)
        {
        }
    }
    else
    {
        std::lock_guard<std::mutex> tex(_log_mutex);
        // 将字符串写入指定流
        fputs(log_str.c_str(), _log_fp);
    }
}

// 刷新缓冲区
void Log::flush()
{
    std::lock_guard<std::mutex> tex(_log_mutex);
    fflush(_log_fp);
}

Log::Log()
{
    DBG_LOG("logger constructed\n");
    _log_count = 0;
}

Log::~Log()
{
    if (_log_async_w_thread)
    {
        _log_thread_stop = true;
        if (_log_async_w_thread->joinable())
        {
            _log_queue->notify();
            _log_async_w_thread->join();
        }
        delete _log_async_w_thread;
    }

    std::lock_guard<std::mutex> mtx(_log_mutex);
    if (_log_fp != nullptr)
        fclose(_log_fp);

    if (_log_buf)
        delete[] _log_buf;

    if (_log_queue)
        delete _log_queue;
}

// 异步线程输入日志
// 使用单独线程向队列读取文件
// 当写入线程没有内容写入队列时，会进入睡眠状态，需要notify唤醒线程
// 当读取线程被意外唤醒，循环会被终止，这种情况不被允许，所以加了一个flag控制循环
void *Log::async_write()
{
    std::string str;
    while (_log_queue->pop(str) && !_log_thread_stop)
    {
        std::lock_guard<std::mutex> mtx(_log_mutex);
        fputs(str.c_str(), _log_fp);
    }
    return nullptr;
}
